#include <string.h>
#include <stdlib.h>
#include "common.h"
#include "byte_code.h"
#include "obj.h"

static uint8_t readByte(Chunk* chunk, int idx);
static uint16_t readShort(Chunk* chunk, int idx);

const char* opCodeStrs[] = {
    "OP_RETURN",
    "OP_CONSTANT",
    "OP_NEGATE",
    "OP_ADD",
    "OP_SUBSTRACT",
    "OP_MULTIPLY",
    "OP_DIVIDE",
    "OP_VARIABLE_DECLARE",
    "OP_ASSIGNMENT",
    "OP_VARIABLE_GET",
    "OP_PRINT",
    "OP_POP",
    "OP_NIL",
    "OP_LOCAL_GET",
    "OP_LOCAL_ASSIGN",
    "OP_JMP_IF_FALSE",
    "OP_JMP_IF_TRUE",
    "OP_JMP",
    "OP_LOOP",
    "OP_LESS",
    "OP_GREATER",
    "OP_EQUAL",
    "OP_NOT",
    "OP_CALL"
};

void chunkInit(Chunk *chunk) {
    vectorInit(&chunk->codes, sizeof(Code));
    vectorInit(&chunk->constants, sizeof(Value));
}

void chunkPrint(Chunk *chunk) {
    printf("\ncodes: \n");
    for (size_t i = 0; i < chunk->codes.size; i++)
    {
        Code* code = vectorGet(&chunk->codes, i);
        const char* name = opCodeStrs[code->code];
        switch (code->code)
        {
        case OP_JMP_IF_FALSE:
        case OP_JMP_IF_TRUE:
        case OP_LOOP:
        case OP_JMP: {
            uint16_t steps = readShort(chunk, i + 1);
            printf("%d. %s %d\n", i, name, steps);
            i += 2;
            break;
        }
        case OP_VARIABLE_DECLARE:
        case OP_ASSIGNMENT:
        case OP_VARIABLE_GET:
        case OP_CONSTANT: {
            uint8_t valIdx = readByte(chunk, i + 1);
            Value val = chunkGetConstant(chunk, valIdx);
            printf("%d. %s %d(", i, name, valIdx);
            valuePrint(&val);
            printf(")\n");
            i++;
            break;
        }
        case OP_LOCAL_ASSIGN:
        case OP_LOCAL_GET: {
            uint8_t stackPos = readByte(chunk, i + 1);
            printf("%d. %s %d\n", i, name, stackPos);
            i++;
            break;
        }
        
        default:
            printf("%d. %s\n", i, name);
            break;
        }
    }

    printf("\nconstants: \n");
    for (size_t i = 0; i < chunk->constants.size; i++)
    {
        Value val = chunkGetConstant(chunk, i);
        printf("%d. ", i, chunkVal2num(val));
        valuePrint(&val);
        printf("\n");
    }
}

void chunkAddCode(Chunk *chunk, OpCode opCode, int line) {
    Code code = {opCode, line};
    // printf("add code: %d\n", opCode);
    vectorAppend(&chunk->codes, &code);
}

void chunkUpdateCode(Chunk *chunk, OpCode opCode, int idx) {
    // printf("add code: %d\n", opCode);
    Code* code = vectorGet(&chunk->codes, idx);
    code->code = opCode;
}

int chunkAddConstant(Chunk *chunk, Value val) {
    // printf("add constant: %.2f\n", val2num(val));
    vectorAppend(&chunk->constants, &val);
    return chunk->constants.size - 1;
}

Code chunkGetCode(Chunk *chunk, int idx) {
    return *(Code*) vectorGet(&chunk->codes, idx);
}

Value chunkGetConstant(Chunk *chunk, int idx) {
    return *(Value*) vectorGet(&chunk->constants, idx);
}

double chunkVal2num(Value val) {
    double num;
    // printf("val2num: %.f\n", val.val.num);
    memcpy(&num, &val.val.num, sizeof(double));
    return num;
}

Value chunkNum2Val(double num) {
    Value val;
    val.type = VAL_NUMBER;
    memcpy(&val.val.num, &num, sizeof(double));
    return val;
}

void valuePrint(Value* val) {
    if(val->type == VAL_NUMBER) {
        double printNum = chunkVal2num(*val);
        printf("%.2f", printNum);
    } else if(val->type == VAL_STRING) {
        ObjString* str = val->val.obj;
        printf("%s", str->val);
    } else if(val->type == VAL_NULL) {
        printf("<NULL>");
    } else if(val->type == VAL_FUNCTION) {
        printf("<fun %s>", ((ObjFunction*)val->val.obj)->name->val);
    } else {
        printf("<unknown %d>", val->type);
    }
}

static uint8_t readByte(Chunk* chunk, int idx) {
    Code code = chunkGetCode(chunk, idx);
    return code.code;
}

static uint16_t readShort(Chunk* chunk, int idx) {
    Code code = chunkGetCode(chunk, idx);
    Code code1 = chunkGetCode(chunk, ++idx);
    return (code.code << 8) | code1.code;
}
